/* Declarations for the runtime interpreter.

   This file is part of khipu.

   khipu is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */

#ifndef __KP_INTERP__
#define __KP_INTERP__   1

#include "defs.hpp"
#include "event.hpp"

KP_DECLS_BEGIN

// Temporary buffer.
struct interp_tbuf
{
  interp_tbuf *next;
};

struct interpreter;

// Valid hook types.
enum
{
  HOOK_TYPE_STKMOV,
  HOOK_TYPE_FCALL,
  HOOK_TYPE_LAST
};

struct interp_hook
{
  unsigned int type;
  void (*cb) (interpreter *, void *);
  void *arg;
  interpreter *interp = nullptr;
  dlist link;

  interp_hook (unsigned int tp,
      void (*cb) (interpreter *, void *), void *arg = nullptr) :
      type (tp), cb (cb), arg (arg)
    {
    }

  bool attach (interpreter *interp);

  bool detach ();

  void call (interpreter *interp)
    {
      this->cb (interp, this->arg);
    }

  ~interp_hook ()
    {
      this->detach ();
    }
};

// References to a GC-visible value.

struct valref_head
{
  interpreter *ip;
  dlist saved;

  valref_head () = delete;

  inline valref_head (interpreter *interp);
  inline ~valref_head ();
};

struct fast_valref
{
  dlist link;
  object value;

  fast_valref ()
    {
      this->link.init_head ();
      this->value = 0;
    }

  inline fast_valref (interpreter *interp, object val = 0);

  void move_fast (fast_valref&& right)
    {
      this->link = right.link;
      this->value = right.value;
      right.link.init_head ();
    }

  object& operator* ()
    {
      return (this->value);
    }

  object operator* () const
    {
      return (this->value);
    }
};

struct valref : public fast_valref
{
  valref () = delete;
  valref (const valref&) = delete;

  inline valref (interpreter *interp, object val = 0) :
      fast_valref (interp, val)
    {
    }

  valref (valref&& right)
    {
      this->link = right.link;
      this->value = right.value;
      right.link.init_head ();
    }

  ~valref ()
    {
      this->link.del ();
    }
};

struct sync_event;
struct memmgr;
struct coroutine;

// Possible interpreter states.
enum
{
  INTERP_RUNNING,
  INTERP_BLOCKING,
  INTERP_SUSPENDED
};

struct exception
{
  object value;
};

struct interpreter
{
  object *stack;
  object *stkend;
  uint32_t cur_frame;
  uint32_t throw_frame;
  uint32_t exc_offset;
  object thread;
  exception last_err;
  object last_tb;
  object stkobj;
  object xpkg;
  // Interpreter registers.
  object retval;
  object alval;
  object aux;
  // Current and saved interpreter state.
  int state;
  int saved_state;
#ifdef KP_HAS_ALLOCA
  int stkrem;
#endif
  bool evh_active;
  bool exc_raised;
  interp_tbuf *tbuf;
  sync_event *evp;
  uint32_t rand_seed;
  uint32_t call_depth;
  uint32_t gc_sem;
  uint32_t npendev;
  intptr_t pendev_mask[NPENDEV / (sizeof (intptr_t) * 8) +
    (NPENDEV % (sizeof (intptr_t) * 8) != 0)];
  dlist values;
  memmgr *mmgr;
  unsigned int num_hooks[HOOK_TYPE_LAST];
  dlist hooks;
  object *tl_syms;
  uintptr_t n_tlsyms;
#ifdef KP_PLATFORM_WINDOWS
  void *io_event;
#endif

  // Internal constants.
  enum
    {
      frame_size = 5
    };

  void init ();

  void reset_exc ()
    {
      this->throw_frame = 0;
      this->exc_raised = false;
      this->last_err.value = this->last_tb = NIL;
    }

  void growstk (uint32_t off);

  object* push (object elem)
    {
      this->growstk (1);
      *this->stkend++ = elem;
      return (this->stkend - 1);
    }

  object pop ()
    {
      return (this->retval = *--this->stkend);
    }

  uint32_t stklen () const
    {
      return ((uint32_t)(this->stkend - this->stack));
    }

  void popn (uint32_t n = 1)
    {
      this->stkend -= n;
    }

  object stktop () const
    {
      return (*(this->stkend - 1));
    }

  void* talloc (size_t size);

  void do_call_hooks (unsigned int type, unsigned int n);
  void call_hooks (unsigned int type)
    {
      unsigned int n = this->num_hooks[type];
      if (kp_unlikely (n))
        this->do_call_hooks (type, n);
    }

#ifdef KP_NO_THREADS
  bool lock ()
    {
      return (false);
    }

  void unlock (bool = true)
    {
    }

  bool lock_remote (interpreter *)
    {
      return (false);
    }

#else
  bool lock ();
  void unlock (bool release = true);
  bool lock_remote (interpreter *interp);
#endif

  void begin_blocking ();
  void end_blocking ();

  struct lock_guard
    {
      interpreter *interp;
      bool rel;

      lock_guard (interpreter *ip) : interp (ip)
        {
          this->rel = this->interp->lock ();
        }

      ~lock_guard ()
        {
          this->interp->unlock (this->rel);
        }
    };

#ifdef KP_NO_THREADS
#  define KP_TLS_INTERP
#elif defined (__clang__) && (defined (__MINGW32__) || defined (__MINGW64__))
#  define KP_TLS_INTERP   __thread
#else
#  define KP_TLS_INTERP   thread_local
#endif

  static KP_TLS_INTERP interpreter *self_interp;

  static interpreter* self () __attribute__ ((const))
    {
      return (interpreter::self_interp);
    }

  static void set_self (interpreter *interp)
    {
      interpreter::self_interp = interp;
    }

  sync_event*& sync_ev ()
    {
      return (this->evp);
    }

  object caller () const;

  void cleanup_talloc (interp_tbuf *cbp);

  uint32_t xrand ();

  void nargs_msg (char *buf, int size,
    int min_argc, int max_argc, int passed);

  [[noreturn]] void raise (object exception);

  [[noreturn]] void raise (const char *exctype, const char *msg);

  [[noreturn]] void raise (const char *exctype, object msg);

  [[noreturn]] void raise_nargs (int min, int max, int passed);

  [[noreturn]] void raise_nargs (object name,
    int min, int max, int passed);

  [[noreturn]] void raise_oob (int idx, int max);

  [[noreturn]] void raise_const ();

  bool exc_handle (void);

  void push_frame (object env, int nargs, int off);

  object dbind_idx (uintptr_t tl_idx, object value);
  object dbind (object sym, object value);
  void unbind (uint32_t n);
  void unbind (uint32_t n, coroutine *crp);

  object stacktrace (uint32_t frame, uint32_t limit);
  object stacktrace (uint32_t frame)
    {
      return (this->stacktrace (frame, 0));
    }

  void set_ev (unsigned int ev);
  void do_handle_evs ();

  void handle_evs ()
    {
      if (kp_unlikely (this->npendev && this->evh_active))
        {
          lock_guard g (this);
          this->do_handle_evs ();
        }
    }

  // User friendly interface for hooks.
  struct hook
    {
      interp_hook base;

      virtual void call (interpreter *) = 0;

      static void hook_cb (interpreter *interp, void *arg)
        {
          ((hook *)arg)->call (interp);
        }

      bool attach (interpreter *interp)
        {
          return (this->base.attach (interp));
        }

      bool detach ()
        {
          return (this->base.detach ());
        }

      hook (unsigned int tp) : base (tp, hook_cb, this) {}
    };
};

struct talloc_cleanup
{
  interpreter *interp;
  interp_tbuf *tbp;
#ifdef KP_HAS_ALLOCA
  int stkrem;
#endif

  talloc_cleanup (interpreter *ip) :
    interp (ip), tbp (ip->tbuf)
#ifdef KP_HAS_ALLOCA
    , stkrem (ip->stkrem)
#endif
      {}

  ~talloc_cleanup ()
    {
      this->interp->cleanup_talloc (this->tbp);
#ifdef KP_HAS_ALLOCA
      this->interp->stkrem = stkrem;
#endif
    }
};

#define KP_TMARK(interp)   talloc_cleanup KP_CONCAT(__tc, __LINE__) (interp)

#ifdef KP_HAS_ALLOCA
#  define KP_TALLOC(interp, size)   \
     ((interp)->stkrem < (int)(size) ? (interp)->talloc (size) :   \
       (((interp)->stkrem -= (size)), KP_ALLOCA (size)))
#else
#  define KP_TALLOC(interp, size)   (interp)->talloc (size)
#endif

fast_valref::fast_valref (interpreter *interp, object val)
{
  this->value = val;
  interp->values.add (&this->link);
}

valref_head::valref_head (interpreter *interp) :
  ip (interp), saved (interp->values)
{
}

valref_head::~valref_head ()
{
  this->ip->values = this->saved;
}

// For event management.
struct evh_guard
{
  bool *swap;

  evh_guard (interpreter *interp)
    {
      if (interp->evh_active)
        {
          *(this->swap = &interp->evh_active) = false;
          interp->handle_evs ();
        }
      else
        this->swap = nullptr;
    }

  ~evh_guard ()
    {
      if (this->swap)
        *this->swap = true;
    }
};

// Disables GC for the calling interpreter.
struct local_gc_guard : public evh_guard
{
  uint32_t *cntp;

  local_gc_guard (interpreter *interp) :
      evh_guard (interp), cntp (&interp->gc_sem)
    {
      ++*this->cntp;
    }

  ~local_gc_guard ()
    {
      --*this->cntp;
    }
};

// Saves and restores the stack pointer.
struct sp_guard
{
  interpreter *interp;
  uint32_t sp;

  sp_guard (interpreter *interp, uint32_t sp) :
      interp (interp), sp (sp)
    {
    }

  sp_guard (interpreter *interp) : sp_guard (interp, interp->stklen ())
    {
    }

  ~sp_guard ()
    {
      this->interp->stkend = this->interp->stack + this->sp;
    }
};

// Saves what's needed in function calls.
struct call_guard : public sp_guard
{
  uint32_t cf;
  uint32_t eoff;

  call_guard (interpreter *interp, uint32_t spadj = 0) :
      sp_guard (interp, interp->stklen () - spadj),
      cf (interp->cur_frame), eoff (interp->exc_offset)
    {
    }

  ~call_guard ()
    {
      this->interp->cur_frame = this->cf;
      this->interp->exc_offset = this->eoff;
    }
};

// Temporarily binds a dynamic symbol.
struct dbinding
{
  interpreter *interp;
  uintptr_t tl_idx;
  valref val;

  dbinding (interpreter *ip, uintptr_t idx, object v);
  ~dbinding ();
};

struct wait_point
{
  interpreter *interp;

  wait_point (interpreter *ip) : interp (ip)
    {
      this->interp->begin_blocking ();
    }

  ~wait_point ()
    {
      this->interp->end_blocking ();
    }
};

// Main thread's interpreter.
KP_EXPORT interpreter *main_interp;

KP_DECLS_END

#endif
